package com.yhb.channel.impl.Scalable;

import com.yhb.channel.ChannelConstant;
import com.yhb.channel.ChannelFactory;
import com.yhb.channel.PayChannel;
import com.yhb.channel.dao.Scalable.ScalableChannelBoundRelationshipMapper;
import com.yhb.channel.dao.Scalable.ScalableChannelBoundRelationshipRecord;
import com.yhb.channel.dao.flow.ExecutionFlow;
import com.yhb.channel.exception.CardAlreadyBoundException;
import com.yhb.channel.exception.PayChannelDeadException;
import com.yhb.channel.exception.RemitCardUnboundException;
import com.yhb.channel.exception.TryingBindCardException;
import com.yhb.channel.impl.ANYPAY.AnyPayConstant;
import com.yhb.channel.impl.Scalable.http.ScalableHttpClient;
import com.yhb.channel.model.*;
import com.yhb.channel.provider.ChannelBaseService;
import com.yhb.channel.provider.INotifyProvider;
import com.yhb.channel.provider.event.EventType;
import com.yhb.channel.provider.event.eventImpls.BJCJ.PollingRemitResultEvent;
import com.yhb.channel.utils.ChannelDateUtil;
import com.yhb.channel.utils.ChannelJSONUtil;
import com.yhb.channel.utils.ChannelLogger;
import com.yhb.channel.utils.ChannelStringUtils;
import lombok.extern.log4j.Log4j;
import lombok.var;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.util.TextUtils;
import org.jetbrains.annotations.NotNull;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Log4j
public class ScalableChannel implements PayChannel {
    /* ------------常量------------ */
    public static final String CHANNEL_BASE_NAME = "第三方通道";
    public static final String CHANNEL_BASE_TYPE = ChannelFactory.SCALABLE;

    /* ----每个子通道不一样的常量----- */
    public String subChannelType;
    public String channelFullName;
    public String channelFullType;
    public String bindcardSmsPrefix;
    public boolean isDHL;
    /* -- TXL是否渠道短信验证支付 -*/
    public boolean payChannelSmsVerifyFlag;

    /* ------------Base Provider Beans or utils------------ */
    private ChannelBaseService channelBaseService;
    private ChannelLogger logger;

    /* ------------Scalable Provider Beans or utils------------ */
    private ScalableHttpClient httpClient;
    private ScalableChannelBoundRelationshipMapper db;

    public ScalableChannel(String subChannelType, @NotNull ChannelBaseService channelBaseService, ScalableHttpClient httpClient, ScalableChannelBoundRelationshipMapper db, ChannelLogger logger) {
        this.subChannelType = subChannelType;
        this.channelBaseService = channelBaseService;
        this.httpClient = httpClient;
        this.db = db;
        this.logger = logger;
        this.channelFullName = CHANNEL_BASE_NAME + '-' + subChannelType;
        this.channelFullType = CHANNEL_BASE_TYPE + '-' + subChannelType;
        this.bindcardSmsPrefix = CHANNEL_BASE_TYPE + "-redis-bindcard-sms-prefix-";
        //判断是否DHL通道
        if(subChannelType.indexOf("DHL") > 0){
            isDHL = true;
            payChannelSmsVerifyFlag = false;
        }else{
            isDHL = false;
            payChannelSmsVerifyFlag = true;
        }
        channelBaseService.eventBus.on(this::eventPayloadParser); // 注册事件到eventPayloadParser
    }

    public void logParams(String methodName, String... params) {
        logger.info("ScalableChannel::" + channelFullType
                + "::" + methodName + "->params=>" + StringUtils.join(params, "|"));
    }

    public String getChannelUserId(String userId) {
        var record = db.getBillingCardRecordByUserId(userId, subChannelType);
        if (record != null) {
            return record.getChannelUserId();
        }
        return "";
    }

    @NotNull
    private HashMap<String, Object> getCommonRequest(String userId, String mobile) {
        var map = new HashMap<String, Object>();
        var requestId = UUID.randomUUID().toString();
        try {
            var location = channelBaseService.channelLocationService.getLocation();
            map.put("location", location.getProvince() + "-" + location.getCity() + "-" + location.getArea());
        } catch (Exception ignored) {}
        var userIdentify = channelBaseService.iUserIdentifyProvider.getUserIdentify(userId);
        String userIdCardNumber = userIdentify.getIdCardNum();
        String userName = userIdentify.getUserName();
        map.put("requestId", requestId);
        map.put("userIdCardNumber", userIdCardNumber);
        map.put("userName", userName);
        try {
            map.put("clientIp", channelBaseService.sessionVarProvider
                    .get(channelBaseService.sessionVarProvider.KEY_CLIENT_IP));
        } catch (Exception ignored) {}
        map.put("mobile", mobile);
        return map;
    }

    @NotNull
    private HashMap<String, Object> getCardInfo(@NotNull IBankCard card, String namespace) {
        var map = new HashMap<String, Object>();
        map.put(ChannelStringUtils.spliceNamespace("bankAccountNo", namespace), card.getNumber());
        map.put(ChannelStringUtils.spliceNamespace("cardType", namespace), card.getCardType());
        map.put(ChannelStringUtils.spliceNamespace("cvn2", namespace), card.getCvv2());
        map.put(ChannelStringUtils.spliceNamespace("expired", namespace), card.getValidation());
        map.put(ChannelStringUtils.spliceNamespace("bankName", namespace), card.getBankName());
        map.put(ChannelStringUtils.spliceNamespace("bankProvince", namespace), card.getProvince());
        map.put(ChannelStringUtils.spliceNamespace("bankCity", namespace), card.getCity());
        map.put(ChannelStringUtils.spliceNamespace("bankArea", namespace), card.getDistrict());
        return map;
    }

    @NotNull
    private HashMap<String, Object> getCardInfo(IBankCard card, String namespace, HashMap<String, Object> oldMap) {
        var newMap = getCardInfo(card, namespace);
        newMap.putAll(oldMap);
        return newMap;
    }

    private boolean checkResStatus(@NotNull HashMap<String, Object> res) {
        var status = (int) res.get("status");
        return status == 0;
    }

    /* ------------业务方法------------ */
    /**
     * 检查是否签约过
     * @param userId 用户
     * @return 是否签约过
     */
    private boolean checkIfRegistered(String userId) {
        String cardId = db.selectBillingCardIdByUserId(userId, subChannelType);
        return !TextUtils.isEmpty(cardId);
    }

    @Override
    public void userSignUp(String userId,
                           String province,
                           String city,
                           String area,
                           @NotNull IBankCard card,
                           String payRate) {
        var methodName = "userSignUp";
        var map = getCommonRequest(userId, card.getPhoneNumber());
        var userAddress = "广东省深圳市春南路2333号糖薰公寓4栋102号";
        var bankAccountNo = card.getNumber();
        var bankName = card.getBankName();
        var bankSubName = card.getSubBankName();
        var bankProvince = card.getProvince();
        var bankCity = card.getCity();
        var bankArea = card.getDistrict();
        var remitFee = "100";
        var userIdentify = channelBaseService.iUserIdentifyProvider.getUserIdentify(userId);
        map.put("userAddress", userAddress);
        map.put("bankAccountNo", bankAccountNo);
        map.put("bankName", bankName);
        map.put("bankSubName", bankSubName);
        map.put("bankProvince", bankProvince);
        map.put("bankCity", bankCity);
        map.put("bankArea", bankArea);
        map.put("remitFee", remitFee);
        map.put("payRate", payRate);
        map.put("idCardFrontImageUrl", userIdentify.getIdCardImgFrontUrl());
        map.put("idCardBackImageUrl", userIdentify.getIdCardImgBackUrl());
        var validations = userIdentify.getValidation().split("-");
        map.put("idCardValidationStart", validations[0]);
        map.put("idCardValidationEnd", validations[1]);
        var res = httpClient.http(methodName, map, channelFullType);
        if (!checkResStatus(res)) {
            logger.fail(ScalableConstant.INNER_ERROR, ChannelStringUtils.stringOr((String) res.get("errorMessage"), "[未知]通道错误"));
        }
        var record = ScalableChannelBoundRelationshipRecord.builder()
                .agreementNo("")
                .userId(userId)
                .cardId(card.getId())
                .bound(0)
                .isBillingCard(1)
                .channelUserId((String) res.get("channelUserInfo"))
                .cardType(card.getCardType())
                .build();
        if (db.getBillingCardRecordByUserId(userId, subChannelType) != null) {
            // 有结算卡 更新结算卡信息
            db.updateByUserId(record, subChannelType);
        } else {
            db.insert(record, subChannelType);
        }
        logger.info(methodName + " success");
    }

    @Override
    public String bindCard(String userId, @NotNull IBankCard card, String clientIP) {
        var methodName = "bindCard";
        var map = getCommonRequest(userId, card.getPhoneNumber());
        var channelUserInfo = getChannelUserId(userId);
        map.put("channelUserInfo", channelUserInfo);
        var fullMap = getCardInfo(card, "", map);
        var res = httpClient.http(methodName, fullMap, channelFullType);
        if (!checkResStatus(res)) {
            var status = (int) res.get("status");
            if (status == 1 || status == 2) {
                var existsRecord = db.getRecordByCardId(card.getId(), subChannelType);
                var channelCardInfo = (String) res.get("channelCardInfo");
                if (existsRecord == null) {
                    var record = ScalableChannelBoundRelationshipRecord.builder()
                            .cardId(card.getId())
                            .bound(1)
                            .isBillingCard(0)
                            .cardType(card.getCardType())
                            .userId(userId)
                            .channelUserId(userId)
                            .agreementNo(channelCardInfo)
                            .build();
                    db.insert(record, subChannelType);
                } else {
                    if (!TextUtils.isEmpty(channelCardInfo)) {
                        existsRecord.setAgreementNo(channelCardInfo);
                    }
                    existsRecord.setBound(1); // 标志绑过卡
                    db.updateByCardId(existsRecord, subChannelType);
                }
                throw new CardAlreadyBoundException();
            }
            logger.fail(ScalableConstant.INNER_ERROR, ChannelStringUtils.stringOr((String) res.get("errorMessage"), "[未知]通道错误"));
        }
        logger.info(methodName + " success");
        var token = (String) res.get("token");
        var payload = ScalableBindCardPayload.builder()
                .card(card)
                .clientIP(clientIP)
                .channelCode(subChannelType)
                .userId(userId)
                .token(token)
                .build();
        String uuid = UUID.randomUUID().toString();
        channelBaseService.redis.boundValueOps(this.bindcardSmsPrefix + uuid).set(
                ChannelJSONUtil.toJson(payload), ChannelConstant.ONE_DAY, TimeUnit.MILLISECONDS);
        return uuid;
    }

    @Override
    public String bindCardConfirm(String smsCode, String userId, String smsUUID) {
        String valStr = channelBaseService.redis.boundValueOps(this.bindcardSmsPrefix + smsUUID).get();
        if (valStr == null || valStr.isEmpty()) {
            logger.fail(ScalableConstant.SMS_EXPIRED, "短信已过期!");
        }
        var payload = ChannelJSONUtil.parseJson(valStr, ScalableBindCardPayload.class);
        var card = payload.getCard();

        var methodName = "bindCardConfirm";
        var map = getCommonRequest(userId, card.getPhoneNumber());
        map.put("token", payload.getToken());
        map.put("smsCode", smsCode);
        map.put("bankAccountNo", card.getNumber());
        var channelUserInfo = getChannelUserId(userId);
        map.put("channelUserInfo", channelUserInfo);

        var res = httpClient.http(methodName, map, channelFullType);
        if (!checkResStatus(res)) {
            logger.fail(ScalableConstant.INNER_ERROR, ChannelStringUtils.stringOr((String) res.get("errorMessage"), "[未知]通道错误"));
        }

        var channelCardInfo = (String) res.get("channelCardInfo");
        var existsRecord = db.getRecordByCardId(card.getId(), subChannelType);
        /*
          判断交易卡是不是和结算卡同一张 防止重复插入同个cardId
         */
        if (existsRecord != null) {
            existsRecord.setAgreementNo(channelCardInfo);
            existsRecord.setBound(1); // 标志绑过卡
            db.updateByCardId(existsRecord, subChannelType);
            return existsRecord.getCardId();
        } else {
            var record = ScalableChannelBoundRelationshipRecord.builder()
                    .cardId(card.getId())
                    .bound(1)
                    .isBillingCard(0)
                    .cardType(card.getCardType())
                    .userId(userId)
                    .channelUserId(userId)
                    .agreementNo(channelCardInfo)
                    .build();
            db.insert(record, subChannelType);
            return card.getId();
        }
    }

    @Override
    public String tryBindCard(String userId, IBankCard card, String clientIP) {
        var location = channelBaseService.channelLocationService.getLocation();
        var province = location.getProvince();
        var city = location.getCity();
        var area = location.getArea();
        var payRate = "0.55";
        return tryBindCard(userId, card, clientIP, payRate, province, city, area);
    }

    @Override
    public String tryBindCard(String userId, @NotNull IBankCard card,
                              String clientIP, String payRate,
                              String province, String city, String area) {
        boolean registered = this.checkIfRegistered(userId);
        int cardType = card.getCardType(); // 0 - 储蓄卡, 1 - 信用卡
        if (!registered) {
            if (cardType == 1) {
                // 未签约且是绑定信用卡 找一张储蓄卡来签约再绑卡
                var signUpCardTmp = channelBaseService.iUserBankCardProvider.getUserDebitCard(userId);
                // 这样拿到的卡是信息不完整的 需要拿cardId换完整的卡
                var cardId = signUpCardTmp.getId();
                var signUpCard = channelBaseService.iUserBankCardProvider.getBankCard(cardId);
                this.userSignUp(userId, province, city, area, signUpCard, payRate);
                return this.bindCard(userId, card, clientIP);
            }
            // 未签约且是绑定储蓄卡直接签约
            this.userSignUp(userId, province, city, area, card, payRate);
        }
        return this.bindCard(userId, card, clientIP);
    }

    @Override
    public String pay(String userId,
                      @NotNull IBankCard payCard,
                      IBankCard remitCard,
                      BigDecimal orderAmount,
                      BigDecimal userFeeAmount,
                      String clientIP,
                      String province,
                      String city,
                      String area,
                      String payRate,
                      String token,
                      String smsCode,
                      String prePayFlowId
    ) throws PayChannelDeadException, TryingBindCardException {
        var methodName = "pay";

        var map = getCommonRequest(userId, payCard.getPhoneNumber());
        var channelUserInfo = getChannelUserId(userId);
        map.put("channelUserInfo", channelUserInfo);

        var payCardRecord = db.getRecordByCardId(payCard.getId(), subChannelType);
        var channelPayCardInfo = payCardRecord.getAgreementNo();
        map.put("channelPayCardInfo", channelPayCardInfo);
        var channelRemitCardInfo = "";
        if (remitCard != null) {
            var remitCardRecord = db.getRecordByCardId(remitCard.getId(), subChannelType);
            if (remitCardRecord != null) {
                channelRemitCardInfo = remitCardRecord.getAgreementNo();
            }
        }
        map.put("channelRemitCardInfo", channelRemitCardInfo);

        var mapWithPayCardInfo = getCardInfo(payCard, "pay", map);
        var mapWithFullCardInfo = mapWithPayCardInfo;
        if (remitCard != null) {
            mapWithFullCardInfo = getCardInfo(remitCard, "remit", mapWithPayCardInfo);
        }

        mapWithFullCardInfo.put("channelPayCardInfo", channelPayCardInfo);

        mapWithFullCardInfo.put("amount", orderAmount.multiply(BigDecimal.valueOf(100L)).longValue() + "");
        mapWithFullCardInfo.put("payRate", payRate);
        mapWithFullCardInfo.put("remitFee", "100");

        mapWithFullCardInfo.put("token", token);
        mapWithFullCardInfo.put("smsCode", smsCode);

        var vipType = channelBaseService.iUserInfoProvider.getUserInfo(userId).getStringVipType();

        var res = httpClient.http(methodName, mapWithFullCardInfo, channelFullType);
        var orderId = "";
        var requestId = (String) mapWithFullCardInfo.get("requestId");
        if (res.containsKey("orderId")) {
            orderId = (String) res.get("orderId");
        }
        HashMap<String, Object> finalMapWithFullCardInfo = mapWithFullCardInfo;
        ExecutionFlow executionFlow = ExecutionFlow.builder()
                .userId(userId)
                .status(ExecutionFlow.FlowStatus.INITIALIZED)
                .type(ExecutionFlow.Type.PAY)
                .channelType(channelFullType)
                .amount(orderAmount)
                .extendInfo(ChannelJSONUtil.toJson(new HashMap<String, String>() {{
                    put("request", ChannelJSONUtil.toJson(finalMapWithFullCardInfo));
                    put("response", ChannelJSONUtil.toJson(res));
                }}))
                .cardId(payCard.getId())
                .requestId(requestId)
                .commission(userFeeAmount)
                .remitFee(BigDecimal.ZERO)
                .orderId(ChannelStringUtils.stringOr(orderId, ""))
                .retryId("")
                .vipType(vipType)
                .createTime(new Date())
                .build();

        if (!checkResStatus(res)) {
            var status = (int) res.get("status");
            if (status == 1) {
                // 支付需要短信 先创建个订单 不返回id 抛TryingBindCardException
                String executionFlowId = channelBaseService.flowService.add(executionFlow);
                logger.info("pay pre success with flow id：" + executionFlowId);
                // 告诉业务方还有短信
                TryPayPayload payload = TryPayPayload.builder()
                        .payCard(payCard)
                        .remitCard(remitCard)
                        .bindCardUUID((String) res.get("token"))
                        .orderAmount(orderAmount)
                        .userFeeAmount(userFeeAmount)
                        .userId(userId)
                        .clientIP(clientIP)
                        .payRate(payRate)
                        .province(province)
                        .city(city)
                        .area(area)
                        .payExecutionFlowId(executionFlowId)
                        .build();
                String msg = channelFullName + " " + payCard.getBankName() + ChannelStringUtils.getMaskString(
                        payCard.getNumber(), 0, 3, 4) + "验证码";
                throw TryingBindCardException.builder()
                        .msg(msg)
                        .phoneNumber(payCard.getPhoneNumber())
                        .channelName(channelFullName)
                        .channelType(channelFullType)
                        .tryPayPayloadJson(ChannelJSONUtil.toJson(payload))
                        .build();
            }
            // 失败了
            var failReason = ChannelStringUtils.stringOr((String) res.get("errorMessage"), "[未知]通道错误");
            executionFlow.setStatus(ExecutionFlow.FlowStatus.FAILED);
            executionFlow.setFailReason(failReason);
            String failId = channelBaseService.flowService.add(executionFlow);
            logger.info("pay fail with flow id：" + failId + ", and failReason is: " + failReason);
            logger.fail(ScalableConstant.INNER_ERROR, failReason);
        }

        if (TextUtils.isEmpty(prePayFlowId)) {
            // 无需短信 且成功 正常创建订单返回id
            String executionFlowId = channelBaseService.flowService.add(executionFlow);
            logger.info("pay success with flow id：" + executionFlowId);
            return executionFlowId;
        } else {
            // 重放成功 返回已经创建的flowId
            var flow = channelBaseService.flowService.getFlowPrecisely(prePayFlowId, new Date()); // 一定是今天的支付订单
            if (flow == null) {
                logger.info("支付确认失败(找不到对应流水) cannot find flow when verifyPay's pay");
                logger.fail(ScalableConstant.ERROR_PARAMETER_WRONG, "支付确认失败(找不到对应流水)");
            }
            assert flow != null;
            flow.setOrderId(orderId); // 补充信息
            channelBaseService.flowService.updatePrecisely(flow, new Date());
            return prePayFlowId;
        }
    }

    @Override
    public String tryPay(String userId, IBankCard payCard, IBankCard remitCard, @NotNull BigDecimal orderAmount, @NotNull BigDecimal userFeeAmount, String clientIP, String payRate, String province, String city, String area) throws TryingBindCardException, PayChannelDeadException {

        logParams("tryPay", userId, ChannelJSONUtil.toJson(payCard),  ChannelJSONUtil.toJson(remitCard),
                orderAmount.toString(), userFeeAmount.toString(), clientIP, payRate, province, city, area);

        if (!this.checkIfBound(payCard, false)) {
            String bindCardUUID = this.tryBindCard(userId, payCard, clientIP, payRate, province, city, area);
            TryPayPayload payload = TryPayPayload.builder()
                    .payCard(payCard)
                    .remitCard(remitCard)
                    .bindCardUUID(bindCardUUID)
                    .orderAmount(orderAmount)
                    .userFeeAmount(userFeeAmount)
                    .userId(userId)
                    .clientIP(clientIP)
                    .payRate(payRate)
                    .province(province)
                    .city(city)
                    .area(area)
                    .payExecutionFlowId(null)
                    .build();
            String msg = CHANNEL_BASE_NAME + " " + payCard.getBankName() + ChannelStringUtils.getMaskString(
                    payCard.getNumber(), 0, 3, 4) + "验证码";
            throw TryingBindCardException.builder()
                    .msg(msg)
                    .phoneNumber(payCard.getPhoneNumber())
                    .channelName(channelFullName)
                    .channelType(channelFullType)
                    .tryPayPayloadJson(ChannelJSONUtil.toJson(payload))
                    .build();
        }

        return this.pay(
                userId,
                payCard, remitCard,
                orderAmount, userFeeAmount,
                clientIP,
                province, city, area,
                payRate,
                "",
                "",
                ""
        );
    }

    @Override
    public String verifyPay(String userId, String smsCode, TryPayPayload payload)
            throws TryingBindCardException, PayChannelDeadException {
        logParams("verifyPay", userId, smsCode,  ChannelJSONUtil.toJson(payload));
        var executionFlowId = payload.getPayExecutionFlowId();
        String uuid = payload.getBindCardUUID();

        if (executionFlowId == null) {
            // 说明是因为绑卡短信重放 此时uuid是绑卡短信token的uuid 可换出绑卡序列号去绑卡确认
            this.bindCardConfirm(smsCode, userId, uuid); // 绑卡成功
            // 接下来 重放消费
            return this.tryPay(
                    userId,
                    payload.getPayCard(), payload.getRemitCard(),
                    payload.getOrderAmount(), payload.getUserFeeAmount(),
                    payload.getClientIP(), payload.getPayRate(),
                    payload.getProvince(), payload.getCity(), payload.getArea()
            );
        } else {
            // 说明是因为支付短信重放
            var flow = channelBaseService.flowService.getFlowPrecisely(executionFlowId, new Date()); // 一定是今天的支付订单
            if (flow == null) {
                logger.info("支付确认失败(找不到对应流水) cannot find flow when verifyPay");
                logger.fail(AnyPayConstant.ERROR_PARAMETER_WRONG, "支付确认失败(找不到对应流水)");
            }
            // 重放消费
            return this.pay(
                    userId,
                    payload.getPayCard(), payload.getRemitCard(),
                    payload.getOrderAmount(), payload.getUserFeeAmount(),
                    payload.getClientIP(),
                    payload.getProvince(), payload.getCity(), payload.getArea(),
                    payload.getPayRate(),
                    uuid, smsCode, executionFlowId
            );
        }
    }

    @Override
    public String remit(String userId, @NotNull IBankCard remitCard, @NotNull IBankCard payCard, BigDecimal amount, BigDecimal userFeeAmount, boolean active, TryBindCardExtendInfo tryBindCardExtendInfo) throws RemitCardUnboundException {
        var methodName = "remit";
        var vipType = channelBaseService.iUserInfoProvider.getUserInfo(userId).getStringVipType();

        var map = getCommonRequest(userId, remitCard.getPhoneNumber());
        var requestId = (String) map.get("requestId");
        var channelUserInfo = getChannelUserId(userId);
        map.put("channelUserInfo", channelUserInfo);

        var remitCardRecord = db.getRecordByCardId(remitCard.getId(), subChannelType);
        var channelRemitCardInfo = "";
        if (remitCardRecord != null) {
            channelRemitCardInfo = remitCardRecord.getAgreementNo();
        }
        map.put("channelRemitCardInfo", channelRemitCardInfo);
        HashMap<String, Object> mapWithRemitCardInfo = null;
        if(isDHL){
            mapWithRemitCardInfo = getCardInfo(payCard, "remit", map);
        }else{
            mapWithRemitCardInfo = getCardInfo(remitCard, "remit", map);
        }
        var _amount = channelFullType.contains("SHANGMENG-DHL") ?
                amount.longValue() + "" :
                amount.multiply(BigDecimal.valueOf(100L)).longValue() + "";
        mapWithRemitCardInfo.put("amount", _amount);
        mapWithRemitCardInfo.put("remitFee", "100");
        var res = httpClient.http(methodName, mapWithRemitCardInfo, channelFullType);

        ExecutionFlow executionFlow = ExecutionFlow.builder()
                .userId(userId)
                .type(active ? ExecutionFlow.Type.ACTIVE_REMIT : ExecutionFlow.Type.REMIT)
                .status(ExecutionFlow.FlowStatus.INITIALIZED)
                .channelType(channelFullType)
                .amount(amount)
                .extendInfo(ChannelJSONUtil.toJson(res))
                .cardId(remitCard.getId())
                .requestId(requestId)
                .commission(BigDecimal.ZERO)
                .remitFee(BigDecimal.ONE)
                .orderId("") // 留空
                .retryId("")
                .vipType(vipType)
                .createTime(new Date())
                .build();

        if (!checkResStatus(res)) {
            var reason = ChannelStringUtils.stringOr((String) res.get("errorMessage"), "[未知]通道错误");
            executionFlow.setStatus(ExecutionFlow.FlowStatus.FAILED);
            executionFlow.setFailReason(reason);
            var flowId = channelBaseService.flowService.add(executionFlow);
            logger.info("remit订单提交失败 流水id：" + flowId + "原因：" + reason);
            channelBaseService.iNotifyProvider.orderStatusNotify(executionFlow,
                    INotifyProvider.OrderStatus.FAILED); // 通知业务失败
            logger.fail(ScalableConstant.INNER_ERROR, reason);
        }
        var orderId = (String) res.get("orderId");
        executionFlow.setOrderId(orderId);
        var flowId = channelBaseService.flowService.add(executionFlow);
        logger.info("remit订单提交成功 流水id：" + flowId);
        return flowId;
    }

    @Override
    public boolean checkIfBound(@NotNull IBankCard card, boolean isRemitCard) {
        if (isRemitCard)
            return true;
        var record = db.getRecordByCardId(card.getId(), subChannelType);
        return !(record == null || record.getBound() == 0);
    }

    @Override
    public boolean checkPayChannelSmsVerify() {
        return payChannelSmsVerifyFlag;
    }

    private void payOrRemitQuery(@NotNull ExecutionFlow flow) {
        String fullType = flow.getChannelType();
        if (!channelFullType.equals(fullType)) {
            return;
        }
        var type = flow.getType();
        var isPay = type.equals(ExecutionFlow.Type.PAY);
        var methodName = isPay ? "payQuery": "remitQuery";
        var cardId = flow.getCardId();
        var card = channelBaseService.iUserBankCardProvider.getBankCard(cardId);
        var mobile = card.getPhoneNumber();
        var userId = flow.getUserId();
        var map = getCommonRequest(userId, mobile);
        map.put("orderId", flow.getOrderId());
        var channelUserInfo = getChannelUserId(userId);
        map.put("channelUserInfo", channelUserInfo);
        var res = httpClient.http(methodName, map, channelFullType);
        var reason = ChannelStringUtils.stringOr((String) res.get("errorMessage"), "[未知]通道错误");
        var status = (int) res.get("status");
        if (status == 1) {
            // 此订单pending
            return;
        }
        if (status == 0) {
            // 此订单成功
            ExecutionFlow successFlow = channelBaseService.flowService.success(flow);
            channelBaseService.iNotifyProvider.orderStatusNotify(successFlow, INotifyProvider.OrderStatus.SUCCEED);
        } else if (status == -1) {
            // 此订单失败
            var requestCtx = flow.getExtendInfo(); // 原本只有流水信息
            var newInfo = ChannelJSONUtil.toJson(new HashMap<String, String>() {{
                put("reason", reason);
                put("requestCtx", requestCtx);
            }});
            flow.setFailReason(reason);
            // 对失败状态和extendInfo落库 并通知业务
            ExecutionFlow failFlow = channelBaseService.flowService.fail(flow, newInfo);
            channelBaseService.iNotifyProvider.orderStatusNotify(failFlow, INotifyProvider.OrderStatus.FAILED);
        }
    }

    @Override
    public void tick() {
        // 查出一周内所有本通道pending态的订单 emit出去POLLING_ORDER_STATUS_EVENT事件
        ArrayList<String> pastDaysList = ChannelDateUtil.getPastDates(7, "yyyyMMdd");
        ArrayList<ExecutionFlow> allFlows = new ArrayList<>();
        for (String pastDate: pastDaysList) {
            if (!channelBaseService.flowService.checkTableExistsWithDate(pastDate)) {
                continue;
            }
            List<ExecutionFlow> flows = channelBaseService
                    .flowService.getPendingFlowsByChannelType(channelFullType, pastDate);
            allFlows.addAll(flows);
        }
        allFlows.forEach(flow -> {
            PollingRemitResultEvent event = new PollingRemitResultEvent(ChannelJSONUtil.toJson(flow));
            channelBaseService.eventBus.emit(event.toJson());
        });
    }

    @Override
    public void eventHandler(@NotNull EventType type, String payload) {
        logger.info("收到 " + type.name() + " 事件");
        switch (type) {
            case POLLING_ORDER_STATUS_EVENT:
                var flow = ChannelJSONUtil.parseJson(payload,
                        ExecutionFlow.class);
                this.payOrRemitQuery(flow);
                break;
            default:
                break;
        }
    }

    @Override
    public ArrayList<ChannelBalance> getBalance(String userId) {
        ArrayList<ChannelBalance> list = new ArrayList<>();
        var record = db.getBillingCardRecordByUserId(userId, subChannelType);
        if (record == null) {
            // 未签约
            logger.info("未签约通道，余额-1");
            list.add(ChannelBalance.builder()
                    .channelName(channelFullName)
                    .channelType(channelFullType)
                    .balance(BigDecimal.valueOf(-1))
                    .build());
            return list;
        }

        var channelUserInfo = record.getChannelUserId();
        var billingCard = channelBaseService.iUserBankCardProvider.getBankCard(record.getCardId());
        var mobile = billingCard.getPhoneNumber();

        var methodName = "walletQuery";
        var map = getCommonRequest(userId, mobile);
        map.put("channelUserInfo", channelUserInfo);

        var recordList = db.getAllRecordByUserId(userId, subChannelType);
        List<String> cardNumberList = new ArrayList<>();
        for(var cardIdList: recordList){
            var card = channelBaseService.iUserBankCardProvider.getBankCard(cardIdList.getCardId());
            if(checkIfBound(card,false)){
                cardNumberList.add(card.getNumber());
            }
        }
        /*
        var cardIdListStream = recordList.parallelStream().map(ScalableChannelBoundRelationshipRecord::getCardId);
        var cardNumberList = cardIdListStream.map(cardId -> {
            var card = channelBaseService.iUserBankCardProvider.getBankCard(cardId);
            if(isDHL){
                if(checkIfBound(card,false)){
                    return null;
                }
            }
            return card.getNumber();
        }).collect(Collectors.toList());
         */
        map.put("cardNumberList", cardNumberList);

        var res = httpClient.http(methodName, map, channelFullType);
        if (!checkResStatus(res)) {
            var reason = ChannelStringUtils.stringOr((String) res.get("errorMessage"), "[未知]通道错误");
            logger.info("查询余额失败" + userId + "原因：" + reason);
            list.add(ChannelBalance.builder()
                    .channelName(channelFullName)
                    .channelType(channelFullType)
                    .balance(BigDecimal.valueOf(-1))
                    .build());
            return list;
        }
        var balanceList = ChannelJSONUtil.parseJson(ChannelJSONUtil.toJson(res.get("balanceList")),
                CardBalance[].class);
        for (var cardBalance: balanceList) {
            var balance = cardBalance.balance;
            var cardNumber = cardBalance.cardNumber;
            list.add(ChannelBalance.builder()
                .channelName(channelFullName)
                .channelType(channelFullType + "$" + cardNumber)
                .balance(
                        BigDecimal.valueOf(Long.parseLong(balance)).divide(BigDecimal.valueOf(100)))
                .build());
        }

        return list;
    }
}
